package dfsketches.terrain_generator;

import java.util.Arrays;
import java.util.Map;
import java.util.Random;

public class MountainedGenerator extends ComplexCosineGenerator {

	/**
	 * @param size Width and height of generated array.
	 * @param parameters Named parameters:init_height, magnitude, magnitude_k, mountain_k.<br>
	 * <li><code>init_height</code> - initial height of map. By default: -200.</li>
	 * <li><code>magnitude</code> - initial magnitude of random heights. By default: 180.</li>
	 * <li><code>magnitude_k</code> - decrement for magnitude. By default: 2.</li>
	 * <li><code>mountain_k</code> - ratio shows how much magnitude depends from height. By default: 1.</li>
	 * <li><code>seed</code> - seed for generator, must be <code>int</code>. By default: any value.</li>
	 */
	@Override
	public int[][] generate(int size, Map<String, Object> parameters) {
		Object oseed = parameters.get("seed");
		long seed = (oseed instanceof Integer) ? (Integer)oseed : System.nanoTime();
		Object oheight = parameters.get("init_height");
		float ih = (oheight instanceof Float) ? (Float)oheight : -200f;
		Object omagnitude = parameters.get("magnitude");
		float magnitude = (omagnitude instanceof Float) ? (Float)omagnitude : 180f;
		Object omagn_koef = parameters.get("magnitude_k");
		float mk = (omagn_koef instanceof Float) ? (Float)omagn_koef : 2f;
		Object okoef = parameters.get("mountain_k");
		float k = ((okoef instanceof Float) ? (Float)okoef : 1f) / magnitude;
		
		rnd = new Random(seed);
		
		float[][] promap = new float[size][size];
		for (float[] ar : promap) {
			Arrays.fill(ar, ih);
		}
		
		for (int parts = 4; parts <= 512; parts += parts) {
			genMap(promap, parts, magnitude, k, ih * 1.0f);
			magnitude /= mk;
		}
		
		int[][] map = new int[size][size];
		for (int x = 0; x < size; x++) {
			int[] mx = map[x];
			float[] pmx = promap[x];
			for (int y = 0; y < size; y++) {
				mx[y] = (int)((pmx[y] - ih) / 1f + ih);
			}
		}
		return map;
	}
	
	private void genMap(float[][] map, int parts, float magnitude, float mount_k, float init_h) {
		int size = map.length;
		int len = size / parts;

		float[][] nodes = genNodes(parts, magnitude, 0f, 0f);

		float k = (float)Math.PI / len;
		float kk = k / len;

		int sx = 0;
		for (int ix = 0; ix < parts; ix ++) {
			int sy = 0;
			for (int iy = 0; iy < parts; iy ++) {
				float a = (nodes[ix][iy] + nodes[ix+1][iy+1]) / 2;
				float b = (nodes[ix][iy] - nodes[ix+1][iy]) / 2;
				float c = (nodes[ix][iy] - nodes[ix][iy+1]) / 2;
				float d = (nodes[ix+1][iy] + nodes[ix][iy+1] - nodes[ix][iy] - nodes[ix+1][iy+1]) / 2;
				for (int x = 0; x < len; x++)
					for (int y = 0; y < len; y++) {
						map[x+sx][y+sy] += (a + b * Math.cos(x * k) +
								c * Math.cos(y * k) + d * Math.cos(x * y * kk)) *
								Math.exp(Math.min((map[x+sx][y+sy] - init_h) * mount_k, 3));
//						map[x+sx][y+sy] += (a + b * Math.cos(x * k) +				//Deprecated formula.
//								c * Math.cos(y * k) + d * Math.cos(x * y * kk)) *
//								(Math.pow(1 + (map[x+sx][y+sy] - init_h) * mount_k, 3));
//						map[x+sx][y+sy] += (a + b * Math.cos(x * k) +
//								c * Math.cos(y * k) + d * Math.cos(x * y * kk));
					}
				sy += len;
			}
			sx += len;
		}
	}

	private float[][] genNodes(int parts, float magnitude, float shift, float border) {
		float[][] nodes = new float[parts + 1][parts + 1]; 
		for (int j = 1; j < parts; j++) {
			float[] jnodes = nodes[j];
			for (int i = 1; i < parts; i++) {
				jnodes[i] = (int) (rnd.nextFloat() * magnitude + shift);
			}
			jnodes[0] = jnodes[parts] = border;
		}
		Arrays.fill(nodes[0], border);
		Arrays.fill(nodes[parts], border);
		return nodes;
	}

}